形参:函数定义时定义的参数;实参:函数调用时定义的参数;
如果函数嵌套两层以上,建立用函数的形式解决嵌套层次太多的问题。
在程序中,将一段代码封装起来,在需要的时候可以直接调用,这些代码可以完成一定的功能和操作,并且可以操纵参数,这就是函数。函数是程序代码最主要的组成部分之一,它将完整的程序分为不同的程序块,有着不同的返回结果。
函数可以看作是由程序员来定义的操作,是划分程序的各个程序块,与内置操作符(一种特殊的函数)相同的是,每个函数都会实现一系列的计算,然后(大多数时候)生成一个计算结果。但与操作符不同的是,函数有自己的函数名,而且操作数没有数量限制。与操作符一样,函数可以重载,这意味着同样的函数名可以对应多个不同的函数。
把相关的语句组合在一起,并且赋予相应的名称,然后用这种方法来给程序分块,这种形式的组合就称之为函数,函数有时候也被称为例程或者过程。
由程序员来编写完成指定任务的函数是用户定义的函数。标准函数库是C提供的可以在任何程序中使用的公共函数,而程序总是从main()函数开始启动的。
函数由函数名以及一组操作数类型唯一地表示。函数的操作数,也即形参,在一对圆括号中声明,形参与形参之间以逗号进行分隔。函数执行的运算在一个称为函数体的块语句中定义。每一个函数都必须有一个相关联的返回类型,定义或者声明函数时,没有显示指定函数的返回类型是不合法的。
递归程序设计:将一个大问题简化为同样形式的较小问题。
在一个递归求解中,分解的子问题与最初的问题具有一样的形式
作为处理问题的工具,递归技术是一种非常有力的工具。利用递归不但可以使得书写复杂度降低,而且使程序看上去更加美观
递归调用:在一个函数中直接或间接地调用函数本身
必须有递归终止的条件
函数决定终止的参数有规略地递增或递减
有可对函数的入口进行测试的基本情况。
if (条件) return (不需要递归的简单答案); else return (递归调用同一函数);
对于大多数常用的递归都有简单、等价的迭代程序。究竟使用哪一种,凭你的经验选择。
迭代程序复杂,但效率高。
递归程序逻辑清晰,但往往效率较低。
设计库的接口:
库的用户必须了解的内容,包括库中函数的原型、这些函数用到的符号常量和自定义类型
接口表现为一个头文件
设计库中的函数的实现:表现为一个源文件
库的这种实现方法称为信息隐藏
函数原型声明和函数定义的区别。
函数原型声明只是说明了该函数应该如何使用,函数调用时应该给它传递哪些数据,函数调用的结果又应该如何使用。函数定义除了给出函数的使用信息外,还需要给出了函数如何实现预期功能,即如何从输入得到输出的完整过程。
什么是形式参数?什么实际参数?
函数的参数一般可以看成是函数运行时的输入。形式参数指出函数调用时应该给它传递几个数据,这些数据是什么类型的。实际参数是函数某次调用时的真正的输入数据,是形式参数的初值。
传递一个数组为什么需要两个参数?
因为数组传递本质上只是传递了数组的起始地址,数组中的元素个数需要另一个变量来指出。
什么是值传递?
在值传递中,形式参数有自己的存储空间,实际参数是形式参数的初值。参数传递完成后,形式参数和实际参数再无任何关联。
为什么要使用库?
库可以实现代码重用。某个项目中各个程序员需要共享一组工具函数时可以将这组函数组成一个库,这些函数的代码在项目中得到了重用。如果另一个项目中也许要这样的一组工具函数,那么这个项目的程序员就不必重新编写这些函数而可以直接使用这个库,这样这组代码在多个项目中得到了重用。
调用库函数要包含文件,调用自定义函数也是如此,当然头文件也可以是包含一些全局常量。
我们知道数组名实际上是数组第一个元素在内存中的地址。类似地,函数名实际上是执行这个函数任务在内存中的开始地址。
所以函数的声明就类似于变量的声明,存储类型标识符 数据类型标识符 函数名(形式参数列表及类型数据);因为是函数,所以有另外的参数列表。
system()会调用shell,而exec()则不会调用shell。system是在单独的进程中执行命令,执行完毕还会回到程序中;而exec()则直接在进程中执行新的程序,新的程序会把原程序覆盖,除非调用出错,否则再也回不到exec()函数后面的代码。
system()会产生新的pid(生成新的shell),而exec()则不会。
C下面的头文件用于处理字符函数,如strstr()、strncat()、strncpy()等;
#include "string.h"
函数调用有三种形式:
1 把函数调用作为一条语句;
2 函数调用出现在一个表达式中;
3 函数调用作为一个函数的实际参数;
函数被调用时,系统为每个形参分配内存单元,也就是相当于一个局部变量的声明后的初始化。
函数可以通过函数指针被调用。
函数之间的通信:
参数和返回值(如果参数是指针或引用,则参数即是输入也是输出,因为他形成了同函数外数据的修改);
函数如何返回一个指针?
1 参数有一指针pc,在函数内再新建一指针p,p=pc,再返回p;
2 不考虑参数是否有指针,在函数内用指针p动态申请(malloc或new)一块内存,再返回p;
函数原型是为了方便编译器查看程序中使用的函数是否正确,函数定义描述了函数如何工作。现代编程习惯是把程序要素分为接口部分和实现部分,例如函数原型和函数定义。接口部分描述了如何使用一个特性,也就是函数原型所在的;实现部分描述了具体的行为,这正是函数定义所做的。接口与实现的分离。
接口是写给使用者看的,当使用者与实现者分离时,就特别有用。
函数的作用:容易理解,方便调试,函数中的变量都是私有局部变量,是对数据的一种保护,函数之间要通信,需要使用指针或引用。通常,如果不想修改指针或引用对应的数据,通常不把其作为左值,如果想在函数体中更新其值,通常用额外的临时变量来实现。如果不想改变指针或引用对应的数据,可以用const修饰为只读。
通常,函数调用都有一定的开销,因为函数的调用过程包括建立调用、传递参数、跳转到函数代码并返回。使用宏使代码内联,可以避免这样的开销。
C99还提供另一种方法:内联函数(inline function)。
函数的原型或声明,是为了在函数调用时方便编译器的检查。
把函数指针加入结构体中可以实现简单的“方法”。
函数包括函数声明、函数定义、函数调用,如果需要修改函数时,最好不需要三个方面都要修改,只需要修改一个地方而保持另外两部分的稳定是最好的。如果接口设计良好的话,则只需要更改函数定义即可。
函数参数除了数组以外默认是以值来传递?为什么是这样,为了数据保护的需要,而数组为什么不是值传递而是按址传递?因为数组包含的数据如果过多时,特别是当数组元素是对象时,所需要的空间和时间都可能会很大,所以,传址就有优势了。
函数原型的声明是为了在多文件的项目中,让项目的语法符合“多次声明,一次定义”的要求。
main()函数对于变量定义的作用域与其它函数是一样的,唯一不同的是,它是整个程序的唯一入口和出口,不需要原型声明。是程序与操作系统交互的界面。
全局变量可以在不同函数之间共享数据,但另一方面,函数中因为使用了全局变量,也让函数的独立性大大降低了。
函数作用域的概念跟变量的存储位置和生命期有关。
作为一名程序开发人员,不可能每次编写都从最底层开发。如要输入一串字符到输出设备上,我们需要做的仅是调用printf()函数,至于其参数是怎样显示的,我们并不关心。
指针函数:
1 可以返回赋值是参数指针或引用的变量;
2 可以返回堆中申请的动态变量;
3 不要返回局部变量地址(一个原则就是在返回调用结束后其值需要仍然存在)。
值传递作为函数的输入;
指针传递作为函数的输出;
函数是程序设计语言中最重要的部分,是模块化设计的主要工具。每一个程序都要用到函数。
即使你自己不定义新的函数, 在每一个完整的C程序中都必须有一个main() 函数。
在C语言中,字符处理、字符串处理和数学计算都是用函数的方式提供的。
函数站在函数设计的角度来看,需要考虑函数的定义,站在函数使用者的角度来看,需要考虑函数的定义和调用;
库函数在调用前需要#include相应的头文件。
自定义的函数在调用时需要进行函数原型说明。
函数原型说明与函数首部写法上需要保持一致,即函数类型、函数名、参数个数和参数顺序必须相同。
如果被调函数的定义在主调函数之前,可以不必加声明。
如果在所有函数定义之前,在函数外部已经做了函数声明,则在主调函数中无须再作声明。
在主程序中计算每个实际参数值。
将实际参数赋给对应的形式参数。在赋值的过程中完成自动类型转换。
依次执行函数体的每个语句,直到遇见return语句或函数体结束
return后面的表达式的值,如果表达式的值与函数的返回类型不一致,则完成类型的转换。
用函数的返回值置换函数,继续主程序的执行
函数之间实现数据共享有以下几种方式:局部变量、全局变量、类的数据成员、类的静态成员和友元。如何共享局部变量呢?可以在主调函数和被调函数之间通过参数传递来共享。全局变量具有文件作用域,所以作用域中的各个函数都能共享全局变量。类的数据成员具有类作用域,能够被类的函数成员共享。
函数之间共享数据也就是此函数访问彼函数的数据主要是通过局部变量(参数传递)、全局变量、类的数据成员(数据成员可以被同一个类中的所有函数成员访问)、类的静态成员(类的所有对象共享)及友元(某个普通函数或者类的成员函数可以访问某个类中的私有数据)实现的。
数据的封装实现了数据的隐藏,让数据更安全,但是前面讲到的通过局部变量、全局变量、类的数据成员、类的静态成员及友元实现了数据的共享,这样又降低了数据的安全性。有些数据是需要共享而又不能被改变的,那么这时候我们就要将其声明为常量。
在声明函数时,参数列表部分可以不指定参数的名称,但是必须指定参数的类型。
如果函数具有多个参数,需要为某些参数提供默认值时,要保证默认值参数应位于非默认值参数的右方,否则将导致编译错误。
如果参数的数据类型是指针类型、引用类型或数组类型,则函数是引用传递,其他情况下是值传递。
In C, arguments to functions are passed “by value.” Suppose the function test is called with the variable num as an argument.
test(num);
The value of num is copied to a temporary location, and this location is passed to test. In this scenario, test has no access whatsoever to the original argument num and, hence, cannot change it in any way.
Does this mean that a function can never change the value of a variable in another function? It can, but in order to do so, it must have access to the address of the variable-the location in memory where the variable is stored.
定义一个函数指针:void (*pFunc)(int);
如果要定义多个同一类型的指针,还可以使用typedef定义一种新的函数指针的数据类型:
typedef void(*PFUNC)(int);
这样就可以使用这种新的数据类型定义函数指针:
PFUNC pFunc1;
PFUNC pFunc2;
这些函数指针可以指向多个相同类型的函数。
函数指针可以使用函数名来赋值,如有一func()函数:
pFunc1 = func(); //前面也可以添加&
用函数指针实现回调函数289/pdf304
回调函数就是函数指针指向的函数。
主调函数使用函数指针作为参数,相当于就是将回调函数嵌入到了主调函数之中。
回调函数可以实现算法的通用性。例如排序算法,你可以定义好算法的通用框架,至于其中核心的算法逻辑,则留待回调函数去完成,用户可以通过不同的回调函数,轻松简单地实现各种算法,对算法进行自定义。
在“传值调用”方式下,一个函数只能产生一个返回值,这个返回值是通过return语句传递回主调用函数中的。如果在一个函数中需要产生一个以上的结果值,可以通过使用全局变量的方式。
函数指针数组:
double (*p[3])()={sin,cos,tan};
因为函数在执行时要依赖于其所在的外部变量。如果将一个函数移到另一个文件中,还要将有关的外部变量及其值一起移过去。但若该外部变量与其他文件的变量同名时,就会出现问题,降低了程序的可靠性和通用性。一般要求把C程序中的函数做成一个封闭体,除了可以通过“实参——形参”的渠道与外界发生联系外,没有其他渠道。
对于递归调用,可以理解为函数体副本机制为n份,其实只有一份,只是局部变量和参数及返回位置在栈中有函数桢的副本。
当调用发生时,程序的执行流程暂时离开主调函数,进入被调函数程序的执行;找到被调函数的程序入口地址,先把实际参数的值传递给对应的形式参数,再开始被调函数体的执行,最后通过return语句把其后表达式的结果返回到主调函数的调用点;程序的执行离开被调函数后,重新回到主调函数中,在调用点对被调函数的返回值进行处理,再继续执行调用点下面的语句。
在调用语句执行时,先把实参的值按对应关系传递给形参变量,作为形参的初始值参与在被调用函数中的运算。
形参:左值声明,实参:右值,赋值;
如果返回void或没有返回值,并不表示只有print,如传引用或地址,即使没有return,则也有可能影响被调函数的数据,也有可能修改全局变量。
求值算术函数使用传值参数,用return返回计算值。
功能性函数用return返回错误信息。
The priority of function:
1 Problem decomposing.
2 easy to read,modify, test.
3 divide between implementer and client.
3 code sharing in project or by library.
4 Algorithm sharing.
In C, interfaces are stored in header files, which tyically end with a .h suffix. Every interface should include several lines of interface boilerplate to ensure that the compiler reads the interface only once.
// main内无代码也要执行一个功能,利用全部变量初始化 #include <stdio.h> int func() { printf("Hello World!"); return 0; } int gi=func(); void main() { }
函数中可以有多个return语句,但每次调用只能有一个return语句被执行,所以只有一个返回值。
一旦遇到return语句,不管后面有没有代码,函数立即运行结束,将值返回。
函数的返回值存储在一个寄存器或两个寄存器中,或在一个寄存器中存储一个内存地址,由这块内存地址来存储返回值。
主调函数和被调用函数之间有数据传递的关系。在不同的函数之间传递数据,可以使用的方法有:
参数:通过形式参数和实际参数
返回值:用return语句返回计算结果
我们知道场面上,C语言拥有两种传递方式:按值传递和按址传递,但是你是否有认真研究过?这里给出一个实质,其实C语言只有按值传递,所谓按址传递只不过是按值传递的一种假象。至于原因稍微一想便能明白。
对于形参和实参而言两个关系紧密,可以这么理解总是实参将自己的一份拷贝传递给形参,这样形参便能安全的使用实参的值,但也带给我们一些麻烦,最经典的交换两数
void swap_v1(int* val_1, int* val_2) { int temp = *val_1; *val_1 = *val_2; *val_2 = *val_1; }
这就是所谓的按址传递,实际上只是将外部指针(实参)的值做一个拷贝,传递给形参val_1与val_2.
函数是子程序,它允许你将大型计算任务分解为较小的计算任务;
函数是由大括号分隔的一段代码,它执行一些明确定义的任务并返回一个值;
函数是程序的构建块,它有助于使现有代码可重用。
函数也可以视为C中的派生类型。
函数是扩展C语言库的一种方法。
函数也有类型,也有sizeof,为其返回值的数据类型所占用的内存空间。
参数传递:在调用函数与被调函数之间的数据复制、读、写;
函数调用时,形参会在栈帧上开辟内存单元,放置实参的值或地址。如果是地址,才是对调用函数内部的变量形成间接访问。
函数执行过程
在主程序中计算每个实际参数值
用实际参数值初始化形式参数
依次执行函数体的每个语句,直到遇见return语句或函数体结束
计算return后面的表达式的值,将表达式的值存储到寄存器eax(当存储不下时,使用两个寄存器,或存储一个内存地址,值存储到内存地址对应的内存空间中)
回到调用函数,用临时变量置换函数调用,继续主程序的执行
函数可以将一段完成独立功能的程序封装起来。通过函数名就可执行这一段功能。使用函数可以将程序模块化
函数要处理的不是指针本身,而是指针所指向的数据。
we defined a function as a collection of statements that execute sequentially. While that is certainly true, that definition doesn’t provide much insight into why functions are useful. Let’s update our definition: A function is a reusable sequence of statements designed to do a particular job.
You already know that every program must have a function named main (which is where the program starts execution when it is run). However, as programs start to get longer and longer, putting all the code inside the main function becomes increasingly hard to manage. Functions provide a way for us to split our programs into small, modular chunks that are easier to organize, test, and use. Most programs use many functions. The C++ standard library comes with plenty of already-written functions for you to use -- however, it’s just as common to write your own. Functions that you write yourself are called user-defined functions.
A function parameter is a variable used in a function. Function parameters work almost identically to variables defined inside the function, but with one difference: they are always initialized with a value provided by the caller of the function.
When a function is called, all of the parameters of the function are created as variables, and the value of each of the arguments is copied into the matching parameter. This process is called pass by value.
函数调用规则,参数顺序
int add(int x, int y) { return x + y; } int main() { int x{ 5 }; int value = add(x, ++x); // is this 5 + 6, or 6 + 6? // It depends on what order your compiler evaluates the function arguments in std::cout << value; // value could be 11 or 12, depending on how the above line evaluates! 12 return 0; }
Pointers are variables that store the memory address of (point at) another variable. The address-of operator (&) can be used to get the address of a variable. The dereference operator (*) can be used to get the value that a pointer points at.
return the address of a variable local to the function, agreed by VC6,but not others:
int* doubleValue(int x) { int value = x * 2; return &value; // return value by address here }
The call stack in action
Let’s examine in more detail how the call stack works. Here is the sequence of steps that takes place when a function is called:
The program encounters a function call.
A stack frame is constructed and pushed on the stack. The stack frame consists of:
The address of the instruction beyond the function call (called the return address). This is how the CPU remembers where to return to after the called function exits.
All function arguments.
Memory for any local variables.
Saved copies of any registers modified by the function that need to be restored when the function returns
The CPU jumps to the function’s start point.
The instructions inside of the function begin executing.
When the function terminates, the following steps happen:
Registers are restored from the call stack
The stack frame is popped off the stack. This frees the memory for all local variables and arguments.
The return value is handled.
The CPU resumes execution at the return address.
Return values can be handled in a number of different ways, depending on the computer’s architecture. Some architectures include the return value as part of the stack frame. Others use CPU registers.
Typically, it is not important to know all the details about how the call stack works. However, understanding that functions are effectively pushed on the stack when they are called and popped off when they return gives you the fundamentals needed to understand recursion, as well as some other concepts that are useful when debugging.